/*
 * IMStorageM.nc -- data abstraction component
 *
 * Copyright (C) Andrey Vihrov <andrey.vihrov@gmail.com>, 2010
 * Copyright (C) Askar Salimbaev <ghostinshell@gmail.com>, 2010
 *
 * This file is part of Intermote.
 *
 * Intermote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Intermote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Intermote.  If not, see <http://www.gnu.org/licenses/>.
 */

//TODO Add initialization and backup
//TODO Allocate first block in initialization if needed

#include <TinyError.h>

#include <Intermote.h>

module IMStorageM {

	provides {
		interface IMStorage as Storage;
		interface SplitControl as StorageControl;
	}

	uses {
		interface IMBlockStorage as BlockStorage;
		interface IMLocalAddress as LocalAddress;

		interface IMBlockProperty as NextBlock;
		interface IMFirstBlock as FirstBlock;

		interface Packet;
		interface AMPacket;
		interface AMSend as FirstBlockSend;
		interface Receive as FirstBlockReceive;
	}

}
implementation {

	/* Save states for split-phase operations */
	enum module_state {
		STATE_OPEN,
		STATE_RSEEK,
		STATE_READ,
		STATE_WRITE,
		STATE_INIT,
			STATE_DROP,
	} state;
	im_off_t current_offset, needed_offset; /* For rseek() */
	im_block_id_t newblock; /* For write() */


	/* The data stream currently being operated on */
	am_addr_t datanode;

	/* First block id of local data stream */
	im_block_id_t first_block;

	/* Block ids and offsets */
	im_block_id_t rblock, wblock;
	im_off_t roffset, woffset;

	/* For drop() */
	im_block_id_t dblock;

	/* Uset supplied buffers */
	struct im_chunk *user_rbuf;
	const struct im_chunk *user_wbuf;


	/* Local functions */

	task void write_noaccess (void)
	{
		/* Access is denied */
		signal Storage.writeDone(EINVAL);
	}


	/* StorageControl interface operations */

	command error_t StorageControl.start (void)
	{
		/* Need to get first block of our stream */
		// TODO Restore from backup

		state = STATE_INIT;
		datanode = call LocalAddress.address();
		call BlockStorage.allocate();
		return SUCCESS;
	}

	command error_t StorageControl.stop (void)
	{
		return FAIL;
	}


	/* Storage interface operations */

	task void do_rseek (void)
	{
		current_offset = 0;

		if (datanode != call LocalAddress.address())
			call FirstBlock.get(datanode);
		else
			signal FirstBlock.getDone(SUCCESS, first_block);
	}

	command void Storage.open (am_addr_t node)
	{
		datanode = node;
		state = STATE_OPEN;
		needed_offset = 0;
		post do_rseek();

		/* No need to adjust write pointer, it is always there if needed */
	}

	command void Storage.rseek (im_off_t offset)
	{
		state = STATE_RSEEK;
		needed_offset = offset;
		post do_rseek();
	}

	command void Storage.read (struct im_chunk *buf)
	{
		if (roffset < IM_BLOCK_SIZE)
			call BlockStorage.read(rblock, roffset, buf);
		else
		{
			state = STATE_READ;
			user_rbuf = buf;
			call NextBlock.get(rblock);
		}
	}

	command void Storage.write (const struct im_chunk *buf)
	{
		if (datanode != call LocalAddress.address())
		{
			post write_noaccess();
			return;
		}

		if (woffset < IM_BLOCK_SIZE)
			call BlockStorage.write(wblock, woffset, buf);
		else
		{
			/* Need to allocate new block */
			user_wbuf = buf;
			state = STATE_WRITE;
			call BlockStorage.allocate();
		}
	}

	command void Storage.reserve (uint8_t count)
	{
		call BlockStorage.reserve(count);
	}

	task void drop_done (void)
	{
		signal Storage.dropDone(SUCCESS);
	}

	command void Storage.drop (void)
	{
		// Drop all blocks preceding rblock
		state = STATE_DROP;
		// Get first block
		if (datanode != call LocalAddress.address()) {
			//TODO: send remote drop request
		}
		else
		{
			if (rblock == first_block)
			{
				/* Nothing to drop */
				post drop_done();
			}
			else
			{
				dblock = first_block;
				call NextBlock.get(dblock);
			}
		}
	}


	/* BlockStorage interface events */

	event void BlockStorage.allocateDone (error_t err, im_block_id_t block)
	{
		switch (state)
		{
			case STATE_WRITE:
				if (err != SUCCESS)
					signal Storage.writeDone(err);
				else
				{
					/* Link new block to the current one */
					newblock = block;
					call NextBlock.set(wblock, newblock);
				}
				break;

			case STATE_INIT:
				if (err == SUCCESS)
					first_block = rblock = wblock = block;

				signal StorageControl.startDone(err);
				break;

			default:
		}
	}

	event void BlockStorage.readDone (error_t err)
	{
		if (err == SUCCESS)
			roffset += IM_CHUNK_SIZE;

		signal Storage.readDone(err);
	}

	event void BlockStorage.writeDone (error_t err)
	{
		if (err == SUCCESS)
			woffset += IM_CHUNK_SIZE;

		signal Storage.writeDone(err);
	}

	event void BlockStorage.eraseDone (error_t err) {
		// Needed for drop
		if(err == SUCCESS)
		{

			if(dblock != rblock)
				call NextBlock.get(dblock);
			else
			{
				signal Storage.dropDone(SUCCESS);
			}
			// Adjust first block
			first_block = dblock;
		} else
			signal Storage.dropDone(FAIL);
	}


	/* FirstBlock interface events */

	event void FirstBlock.getDone (error_t err, im_block_id_t id)
	{
		/* Treat this block like a regular one */
		signal NextBlock.getDone(err, id);
	}


	/* NextBlock interface events */

	event void NextBlock.setDone (error_t err)
	{
		if (err != SUCCESS)
			signal Storage.writeDone(err);
		else
		{
			wblock = newblock;
			woffset = 0;
			call Storage.write(user_wbuf);
		}
	}

	event void NextBlock.getDone (error_t err, uint32_t data)
	{
		switch (state)
		{
			case STATE_RSEEK:
			case STATE_OPEN:
				if (err != SUCCESS)
				{
					if (state == STATE_RSEEK)
						signal Storage.rseekDone(err);
					else
						signal Storage.openDone(err);
				}
				else
				{
					current_offset += IM_BLOCK_SIZE;

					if (needed_offset < current_offset)
					{
						rblock = data;
						roffset = needed_offset % IM_BLOCK_SIZE;

						if (state == STATE_RSEEK)
							signal Storage.rseekDone(SUCCESS);
						else
							signal Storage.openDone(SUCCESS);
					}
					else
						call NextBlock.get(data);
				}

				break;

			case STATE_READ:
				if (err != SUCCESS)
					signal Storage.readDone(err);
				else
				{
					if (data != IM_NOBLOCK)
					{
						rblock = data;
						roffset = 0;
						call Storage.read(user_rbuf);
					}
					else
						signal Storage.readDone(EEOF);
				}

				break;
			case STATE_DROP:
				if (err != SUCCESS)
					signal Storage.dropDone(err);
				else
				{
					call BlockStorage.erase(dblock);
					dblock = data;
				}

				break;

			default:
		}
	}


	/* First block requests */

	event message_t *FirstBlockReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		static message_t reply;

		nx_uint32_t *data;

		call Packet.clear(&reply);
		data = call Packet.getPayload(&reply, sizeof(nx_uint32_t));
		if (!data)
			return msg;
		*data = first_block;

		call FirstBlockSend.send(call AMPacket.source(msg), &reply, sizeof(nx_uint32_t));

		return msg;
	}

	event void FirstBlockSend.sendDone (message_t *msg, error_t err)
	{

	}

}
